home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Cream of the Crop 21
/
Cream of the Crop 21 (Terry Blount) (October 1996).iso
/
os2
/
e33el2.zip
/
emacs
/
19.33
/
lisp
/
cal-dst.el
< prev
next >
Wrap
Lisp/Scheme
|
1996-01-20
|
17KB
|
388 lines
;;; cal-dst.el --- calendar functions for daylight savings rules.
;; Copyright (C) 1993, 1994, 1995 Free Software Foundation, Inc.
;; Author: Paul Eggert <eggert@twinsun.com>
;; Edward M. Reingold <reingold@cs.uiuc.edu>
;; Keywords: calendar
;; Human-Keywords: daylight savings time, calendar, diary, holidays
;; This file is part of GNU Emacs.
;; GNU Emacs is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
;;; Commentary:
;; This collection of functions implements the features of calendar.el and
;; holiday.el that deal with daylight savings time.
;; Comments, corrections, and improvements should be sent to
;; Edward M. Reingold Department of Computer Science
;; (217) 333-6733 University of Illinois at Urbana-Champaign
;; reingold@cs.uiuc.edu 1304 West Springfield Avenue
;; Urbana, Illinois 61801
;;; Code:
(require 'calendar)
(defvar calendar-current-time-zone-cache nil
"Cache for result of calendar-current-time-zone.")
(defvar calendar-system-time-basis
(calendar-absolute-from-gregorian '(1 1 1970))
"Absolute date of starting date of system clock.")
(defun calendar-absolute-from-time (x utc-diff)
"Absolute local date of time X; local time is UTC-DIFF seconds from UTC.
X is (HIGH . LOW) or (HIGH LOW . IGNORED) where HIGH and LOW are the
high and low 16 bits, respectively, of the number of seconds since
1970-01-01 00:00:00 UTC, ignoring leap seconds.
Returns the pair (ABS-DATE . SECONDS) where SECONDS after local midnight on
absolute date ABS-DATE is the equivalent moment to X."
(let* ((h (car x))
(xtail (cdr x))
(l (+ utc-diff (if (numberp xtail) xtail (car xtail))))
(u (+ (* 512 (mod h 675)) (floor l 128))))
;; Overflow is a terrible thing!
(cons (+ calendar-system-time-basis
;; floor((2^16 h +l) / (60*60*24))
(* 512 (floor h 675)) (floor u 675))
;; (2^16 h +l) mod (60*60*24)
(+ (* (mod u 675) 128) (mod l 128)))))
(defun calendar-time-from-absolute (abs-date s)
"Time of absolute date ABS-DATE, S seconds after midnight.
Returns the pair (HIGH . LOW) where HIGH and LOW are the high and low
16 bits, respectively, of the number of seconds 1970-01-01 00:00:00 UTC,
ignoring leap seconds, that is the equivalent moment to S seconds after
midnight UTC on absolute date ABS-DATE."
(let* ((a (- abs-date calendar-system-time-basis))
(u (+ (* 163 (mod a 512)) (floor s 128))))
;; Overflow is a terrible thing!
(cons
;; floor((60*60*24*a + s) / 2^16)
(+ a (* 163 (floor a 512)) (floor u 512))
;; (60*60*24*a + s) mod 2^16
(+ (* 128 (mod u 512)) (mod s 128)))))
(defun calendar-next-time-zone-transition (time)
"Return the time of the next time zone transition after TIME.
Both TIME and the result are acceptable arguments to current-time-zone.
Return nil if no such transition can be found."
(let* ((base 65536);; 2^16 = base of current-time output
(quarter-multiple 120);; approx = (seconds per quarter year) / base
(time-zone (current-time-zone time))
(time-utc-diff (car time-zone))
hi
hi-zone
(hi-utc-diff time-utc-diff)
(quarters '(2 1 3)))
;; Heuristic: probe the time zone offset in the next three calendar
;; quarters, looking for a time zone offset different from TIME.
(while (and quarters (eq time-utc-diff hi-utc-diff))
(setq hi (cons (+ (car time) (* (car quarters) quarter-multiple)) 0))
(setq hi-zone (current-time-zone hi))
(setq hi-utc-diff (car hi-zone))
(setq quarters (cdr quarters)))
(and
time-utc-diff
hi-utc-diff
(not (eq time-utc-diff hi-utc-diff))
;; Now HI is after the next time zone transition.
;; Set LO to TIME, and then binary search to increase LO and decrease HI
;; until LO is just before and HI is just after the time zone transition.
(let* ((tail (cdr time))
(lo (cons (car time) (if (numberp tail) tail (car tail))))
probe)
(while
;; Set PROBE to halfway between LO and HI, rounding down.
;; If PROBE equals LO, we are done.
(let* ((lsum (+ (cdr lo) (cdr hi)))
(hsum (+ (car lo) (car hi) (/ lsum base)))
(hsumodd (logand 1 hsum)))
(setq probe (cons (/ (- hsum hsumodd) 2)
(/ (+ (* hsumodd base) (% lsum base)) 2)))
(not (equal lo probe)))
;; Set either LO or HI to PROBE, depending on probe results.
(if (eq (car (current-time-zone probe)) hi-utc-diff)
(setq hi probe)
(setq lo probe)))
hi))))
(defun calendar-time-zone-daylight-rules (abs-date utc-diff)
"Return daylight transition rule for ABS-DATE, UTC-DIFF sec offset from UTC.
ABS-DIFF must specify a day that contains a daylight savings transition.
The result has the proper form for calendar-daylight-savings-starts'."
(let* ((date (calendar-gregorian-from-absolute abs-date))
(weekday (% abs-date 7))
(m (extract-calendar-month date))
(d (extract-calendar-day date))
(y (extract-calendar-year date))
(last (calendar-last-day-of-month m y))
(candidate-rules
(append
;; Day D of month M.
(list (list 'list m d 'year))
;; The first WEEKDAY of month M.
(if (< d 8)
(list (list 'calendar-nth-named-day 1 weekday m 'year)))
;; The last WEEKDAY of month M.
(if (> d (- last 7))
(list (list 'calendar-nth-named-day -1 weekday m 'year)))
;; The first WEEKDAY after day J of month M, for D-6 < J <= D.
(let (l)
(calendar-for-loop j from (max 2 (- d 6)) to (min d (- last 8)) do
(setq l
(cons
(list 'calendar-nth-named-day 1 weekday m 'year j)
l)))
l)))
(prevday-sec (- -1 utc-diff)) ;; last sec of previous local day
(year (1+ y)))
;; Scan through the next few years until only one rule remains.
(while
(let ((rules candidate-rules)
new-rules)
(while
(let*
((rule (car rules))
(date
;; The following is much faster than
;; (calendar-absolute-from-gregorian (eval rule)).
(cond ((eq (car rule) 'calendar-nth-named-day)
(eval (cons 'calendar-nth-named-absday (cdr rule))))
((eq (car rule) 'calendar-gregorian-from-absolute)
(eval (car (cdr rule))))
(t (let ((g (eval rule)))
(calendar-absolute-from-gregorian g))))))
(or (equal
(current-time-zone
(calendar-time-from-absolute date prevday-sec))
(current-time-zone
(calendar-time-from-absolute (1+ date) prevday-sec)))
(setq new-rules (cons rule new-rules)))
(setq rules (cdr rules))))
;; If no rules remain, just use the first candidate rule;
;; it's wrong in general, but it's right for at least one year.
(setq candidate-rules (if new-rules (nreverse new-rules)
(list (car candidate-rules))))
(setq year (1+ year))
(cdr candidate-rules)))
(car candidate-rules)))
(defun calendar-current-time-zone ()
"Return UTC difference, dst offset, names and rules for current time zone.
Returns (UTC-DIFF DST-OFFSET STD-ZONE DST-ZONE DST-STARTS DST-ENDS
DST-STARTS-TIME DST-ENDS-TIME), based on a heuristic probing of what the
system knows:
UTC-DIFF is an integer specifying the number of minutes difference between
standard time in the current time zone and Coordinated Universal Time